import {
  BleConnection
} from "./inner_ble_connection"
import {
  CRC32
} from './CRC32.js';

const bx_ota_service_uuid = "00002600-0000-1000-8000-00805F9B34FB"
const bx_ota_ctrl_uuid = "00007000-0000-1000-8000-00805F9B34FB"
const bx_ota_data_uuid = "00007001-0000-1000-8000-00805F9B34FB"
const bx_ota_cmd_code = {
  request_start: 0,
  response_start: 1,
  request_block: 2,
  request_transfer_end: 3
}

async function basicProcess(bleConnection, rawData, hexAddress, mtuSize, onProgressChanged) {
  let maxByteLenInSegment = mtuSize - 4
  if (maxByteLenInSegment >= 29) {
    //Android S(31)以上的设备如果每包的数据等于 MTUSize-4 的话就会容易丢包
    maxByteLenInSegment -= 10
  }
  //maxSegmentsLenInBlock： 一个Block最多可以容纳多少个Segment
  let maxSegmentsLenInBlock = await monitorIndicateAndStartOTA(bleConnection, rawData, hexAddress, maxByteLenInSegment)
  let blocks = spiltDataWhenStartResponse(rawData, maxByteLenInSegment, maxSegmentsLenInBlock)
  console.log(`待发送${blocks.length}个Block`)
  // let cb = function (progress) {
  //   console.log("progress = " + progress)
  // }
  return transferData2(bleConnection, blocks, onProgressChanged)
}

/**
 * @param {BleConnection} connection 
 * @param {ArrayBuffer} rawData 
 * @param {String} hexAddress HexString?
 * @param {Number} maxByteLenInSegment 
 */
function monitorIndicateAndStartOTA(connection, rawData, hexAddress, maxByteLenInSegment) {
  function requestStartOTA(connection, rawData, hexAddress, maxByteLenInSegment) {
    let crc = new CRC32()
    let u8Array = new Uint8Array(rawData)
    let signedCrcValue = crc.crc32(u8Array, 0, rawData.byteLength) //有符号
    let unsignedCrc32Value = signedCrcValue >>> 0 //无符号
    let type = (hexAddress == null || hexAddress == "") ? 0 : 1
    let ab = new ArrayBuffer(16)
    var dv = new DataView(ab)
    dv.setInt8(0, bx_ota_cmd_code.request_start)
    // dv.setUint16(1, maxByteLenInSegment, true)
    dv.setUint16(1, 19, true)
    dv.setUint32(3, unsignedCrc32Value, true)
    dv.setUint32(7, rawData.byteLength, true)
    dv.setInt8(11, type)

    if (hexAddress == null || hexAddress == "") {
      return new Promise((resolve, reject) => {
        let cmd1 = ab.slice(0, 12)
        connection.writeBuffer(bx_ota_service_uuid, bx_ota_ctrl_uuid, true, cmd1).then(
          (res) => {
            resolve(res)
          },
          (err) => {
            let cmd2 = ab.slice(0, 11)
            connection.writeBuffer(bx_ota_service_uuid, bx_ota_ctrl_uuid, true, cmd2).then((res) => {
              resolve(res)
            }, (err) => {
              reject(err)
            })
          }
        )
      })
    } else {
      let address = parseInt(hexAddress, 16)
      dv.setUint32(12, address, true)

      let log = buf2hex(ab)
      console.log(`cmd1 = ${log}`)
      return connection.writeBuffer(bx_ota_service_uuid, bx_ota_ctrl_uuid, true, ab)
    }
  }

  function parseIndicate(res, deviceId) {
    let isTarget = res.deviceId == deviceId
    let isService = res.serviceId == bx_ota_service_uuid
    let isChara = res.characteristicId == bx_ota_ctrl_uuid
    let dataView = new DataView(res.value)
    let responseCode = dataView.getInt8(0)
    let result = dataView.getInt8(1)
    //21 * 8 == 168 一个Block中可能有1~168个segments
    let maxSegmentNumInBlock = dataView.getInt8(2) * 8
    return {
      isStartResponse: isTarget && isService && isChara && (responseCode == bx_ota_cmd_code.response_start),
      resultCode: result,
      maxSegmentsLenInBlock: maxSegmentNumInBlock
    }
  }

  return new Promise((resolve, reject) => {
    let timeout = setTimeout(function () {
      reject("Timeout: no start response received")
    }, 5000)
    const callback = function (res) {
      console.log(res)
      let model = parseIndicate(res, connection.properties.deviceId)
      if (model.isStartResponse) {
        clearTimeout(timeout)
        if (model.resultCode == 0) {
          wx.offBLECharacteristicValueChange(callback)

          setTimeout(function () {
            resolve(model.maxSegmentsLenInBlock)
          }, 300)

        } else {
          wx.offBLECharacteristicValueChange(callback)
          reject("Remote device reject OTA, errorCode: " + model.resultCode)
        }
      }
    }
    wx.onBLECharacteristicValueChange(callback)
    requestStartOTA(connection, rawData, hexAddress, maxByteLenInSegment).then(
      (res) => {
        console.log(res)
      }
    ).catch(function (err) {
      clearTimeout(timeout)
      wx.offBLECharacteristicValueChange(callback)
      console.error("Request Start OTA failed, reason" + err)
      reject(err)
    })
  })
}

/**
 * 第二步：收到Response（indicate），解析数据
 * 一个Block中可能有 [1, maxSegmentsLenInBlock] 个segments
 * @param {number} maxSegmentsLenInBlock 
 */
function spiltDataWhenStartResponse(rawData, maxByteLenInSegment, maxSegmentsLenInBlock) {

  function buildBlock( /*number */ index, /*ArrayBuffer*/ blockData, /*number */ maxByteLenInSegment) /**block */ {
    let segments = [] //ArrayBuffer Array
    let segmentSize = Math.floor(blockData.byteLength / maxByteLenInSegment)
    let remain = blockData.byteLength % maxByteLenInSegment
    if (blockData.byteLength < 500) {
      console.log(`maxByteLenInSegment=${maxByteLenInSegment};`)
      console.log(`segmentSize=${segmentSize}; remain=${remain}`)
    }
    for (let i = 0; i < segmentSize; i++) {
      let start = maxByteLenInSegment * i
      let end = maxByteLenInSegment * (i + 1)
      let indexBuffer = new Uint8Array([i]).buffer

      let contentBuffer = blockData.slice(start, end)
      let segmentData = appendBuffer(indexBuffer, contentBuffer)
      let segment = {
        index: i,
        data: segmentData //arrayBuffer
      }
      segments.push(segment)
    }
    if (remain > 0) {
      let start = maxByteLenInSegment * segmentSize
      let end = start + remain
      let indexBuffer = new Uint8Array([segmentSize]).buffer
      let contentBuffer = blockData.slice(start, end)
      let segmentData = appendBuffer(indexBuffer, contentBuffer)
      let segment = {
        index: segmentSize,
        data: segmentData
      }
      segments.push(segment)
    }
    let requestBlockCmdBuffer = new ArrayBuffer(3)
    let dataView = new DataView(requestBlockCmdBuffer)
    dataView.setInt8(0, bx_ota_cmd_code.request_block)
    dataView.setUint16(1, index, true)
    return {
      index: index,
      blockData: blockData, //block raw data
      requestCmd: requestBlockCmdBuffer,
      segments: segments
    } //block
  }

  function appendBuffer(buffer1, buffer2) {
    var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
    tmp.set(new Uint8Array(buffer1), 0);
    tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
    return tmp;
  };

  let maxByteLenInBlock = maxByteLenInSegment * maxSegmentsLenInBlock
  let blocks = []
  var blockSize = Math.floor(rawData.byteLength / maxByteLenInBlock)
  let remain = rawData.byteLength % maxByteLenInBlock
  for (let i = 0; i < blockSize; i++) {
    let start = maxByteLenInBlock * i
    let end = maxByteLenInBlock * (i + 1)
    let blockData = rawData.slice(start, end)
    let block = buildBlock(i, blockData, maxByteLenInSegment)
    blocks.push(block)
  }
  if (remain > 0) {
    let start = maxByteLenInBlock * blockSize
    let end = start + remain
    let blockData = rawData.slice(start, end)
    let block = buildBlock(blockSize, blockData, maxByteLenInSegment)
    blocks.push(block)
  }
  return blocks
}

// /**
//  * 好像不适合用Promise.all
//  */
// function transferData(connection, blocks, onProgressChanged) {
//   let writeTasks = []
//   for (let i = 0; i < blocks.length; i++) {
//     let block = blocks[i]
//     //第三步，向设备申请新的Block写入数据
//     let writeBlockTask = connection.writeBuffer(bx_ota_service_uuid, bx_ota_ctrl_uuid, true, block.requestCmd)
//     writeTasks.push(writeBlockTask)
//     //向外面输出progress
//     let progressTask = new Promise((resolve, reject) => {
//       let currentProgress = i / blocks.length
//       var currentTime = new Date(new Date().setHours(new Date().getHours() + 8)).toISOString().replace("Z", " ").replace("T", " ");
//       console.log({
//         currentTime
//       });
//       onProgressChanged(currentProgress)
//       resolve()
//     })
//     writeTasks.push(progressTask)
//     for (let segIndex = 0; segIndex < block.segments.length; segIndex++) {
//       let segment = block.segments[segIndex]
//       //第四步，向设备写入Segment数据，写完所有segments后回到第三步
//       let writeSegmentTask = connection.writeBuffer(bx_ota_service_uuid, bx_ota_data_uuid, false, segment.data.buffer)
//       writeTasks.push(writeSegmentTask)
//     }
//   }

//   function transferEnd() {
//     return new Promise((resolve, reject) => {
//       let transferEndCmd = new Uint8Array([bx_ota_cmd_code.request_transfer_end])
//       //这里可能write failed，有的版本会在蓝牙返回response前重启了，导致write failed，有的版本会返回成功
//       connection.writeBuffer(bx_ota_service_uuid, bx_ota_ctrl_uuid, true, transferEndCmd.buffer).then((res) => {
//         resolve(res)
//       }, (err) => {
//         resolve("")
//       })
//     })
//   }
//   //第5步 发送传输结束命令
//   let writeEndTask = transferEnd()
//   writeTasks.push(writeEndTask)
//   //向外面输出progress
//   let progressTask = new Promise((resolve, reject) => {
//     onProgressChanged(1)
//     resolve()
//   })
//   writeTasks.push(progressTask)
//   return Promise.all(writeTasks)
// }

function transferData2(connection, blocks, onProgressChanged) {
  return new Promise((resolve, reject) => {
    var blockIndex = 0
    execute()

    function execute() {
      if (blockIndex < blocks.length) {
        onProgressChanged(blockIndex / blocks.length)
        let currentBlock = blocks[blockIndex]
        requestBlock(connection, currentBlock,
          () => {
            blockIndex++
            execute()
          },
          (err) => {
            reject(err)
          }
        )
      } else {
        onProgressChanged(1)
        let transferEndCmd = new Uint8Array([bx_ota_cmd_code.request_transfer_end])
        connection.writeBuffer(bx_ota_service_uuid, bx_ota_ctrl_uuid, true, transferEndCmd.buffer).then((res) => {
          resolve(res)
        }, (err) => {
          resolve("")
        })
      }
    }
  })
}

///请求Block，并传输segment
function requestBlock(ble, block, onSuccess, onFailed) {
  console.log("request block " + block.index)
  let cmd = block.requestCmd


  ble.writeBuffer(bx_ota_service_uuid, bx_ota_ctrl_uuid, true, cmd).then(
    (res) => {
      execute()
    }, (err) => {
      onFailed(err)
    }
  )


  let segmentIndex = 0

  function execute() {
    if (segmentIndex < block.segments.length) {
      let segBuf = block.segments[segmentIndex].data.buffer
      ble.writeBuffer(bx_ota_service_uuid, bx_ota_data_uuid, false, segBuf).then(
        (res) => {
          segmentIndex++
          execute()
        }, (err) => {
          onFailed(err)
        }
      )
    } else {
      onSuccess()
    }
  }
}

function buf2hex(buffer) { // buffer is an ArrayBuffer
  return [...new Uint8Array(buffer)]
    .map(x => x.toString(16).padStart(2, '0'))
    .join(' ');
}

module.exports = {
  executeBasicProcess: basicProcess
}